package com.szeldon.declatest.exec.plugins;

import java.io.IOException;
import java.util.Date;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import com.szeldon.declatest.declaration.TestsSuiteDeclaration;
import com.szeldon.declatest.declaration.endpoints.EndpointDeclaration;
import com.szeldon.declatest.declaration.scenario.ScenarioDeclaration;
import com.szeldon.declatest.declaration.scenario.parts.DoScenarioStepPartDeclaration;
import com.szeldon.declatest.declaration.settings.JmsSettingsPartDeclaration;
import com.szeldon.declatest.declaration.settings.SettingsDeclaration;
import com.szeldon.declatest.declaration.steps.ScenarioStepDeclaration;
import com.szeldon.declatest.exec.ITestSuiteExecutorListener;
import com.szeldon.declatest.exec.TestSuiteExecHistory;
import com.szeldon.declatest.exec.events.TestSuiteExecEvent;
import com.szeldon.declatest.exec.events.dostep.jms.JmsMessageReceivedExecEvent;
import com.szeldon.declatest.exec.events.dostep.jms.JmsMessageSentExecEvent;
import com.szeldon.declatest.exec.exceptions.ScenarioExecutionException;
import com.szeldon.utils.FileUtils;
import com.szeldon.utils.ValuesUtils;

/**
 * Plugin for executing DoScenarioSteps with WebService type of endpoint.
 * 
 * @author szeldon
 * 
 */
public class JMSDoScenarioPartExecutorPlugin implements IDoScenarioStepExecutorPlugin {

    private static Logger logger = Logger.getLogger(JMSDoScenarioPartExecutorPlugin.class.getName());

    public static final String JMS_ENDPOINT_TYPE = "JMS";

    public static final String JMS_QUEUE_DESTINATION_NAME = "queue";
    public static final String JMS_TOPIC_DESTINATION_NAME = "topic";
    public static final String JMS_SEND_ACTIVITY_TYPE = "send";
    public static final String JMS_RECEIVE_ACTIVITY_TYPE = "receive";

    @Override
    public String getSupportedEndpointType() {
        return JMS_ENDPOINT_TYPE;
    }

    @Override
    public void execute(DoScenarioStepPartDeclaration doScenarioStepPart, ScenarioStepDeclaration step,
            TestsSuiteDeclaration suite, Properties properties, EndpointDeclaration endpoint,
            SettingsDeclaration settings, ScenarioDeclaration scenario, TestSuiteExecHistory history,
            ITestSuiteExecutorListener listener) {
        logger.info("Starting execution in JMS executor plugin.");

        checkArgs(doScenarioStepPart, suite, properties, endpoint, settings, scenario, history);

        JmsSettingsPartDeclaration jmsSettings = (JmsSettingsPartDeclaration) settings;

        try {
            logger.fine("Creating context.");
            Context jndiContext = new InitialContext();
            jndiContext.addToEnvironment(Context.INITIAL_CONTEXT_FACTORY,
                    "org.apache.activemq.jndi.ActiveMQInitialContextFactory");
            jndiContext.addToEnvironment(Context.PROVIDER_URL, endpoint.getUrl());
            String destinationType = jmsSettings.getDestinationType();
            String destinationName = jmsSettings.getDestinationName();
            String activityType = jmsSettings.getActivityType();

            logger.fine("Initial context created.");

            if (destinationType.equals(JMS_QUEUE_DESTINATION_NAME)) {
                logger.fine("Looking up queue connection factory.");
                QueueConnectionFactory queueConnectionFactory = (QueueConnectionFactory) jndiContext
                        .lookup("QueueConnectionFactory");
                logger.finest("Looking up queue: " + destinationName);
                Queue queue = (Queue) jndiContext.lookup(destinationName);
                logger.finest("Creating queue connection.");
                QueueConnection queueConnection = queueConnectionFactory.createQueueConnection();
                queueConnection.start();
                logger.finest("Creating queue session.");
                QueueSession queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);

                if (activityType.equals(JMS_SEND_ACTIVITY_TYPE)) {
                    logger.finest("Creating queue sender.");
                    QueueSender queueSender = queueSession.createSender(queue);
                    logger.finest("Reading file: " + step.getRequestPath());
                    String requestContent = FileUtils.readFileContent(step.getRequestPath());
                    logger.finest("Creating text message.");
                    TextMessage textMessage = queueSession.createTextMessage(requestContent);
                    logger.fine("Sending text message.");
                    queueSender.send(textMessage);

                    processEvent(
                            new JmsMessageSentExecEvent(scenario.getId(), "JMS message sent.", new Date(),
                                    step.getId(), step.getDescription(), step.getEndpointId(), step.getRequestPath(),
                                    endpoint.getDescription(), endpoint.getUrl(), endpoint.getSettingsId(),
                                    destinationType, destinationName, requestContent), history, listener);

                } else if (activityType.equals(JMS_RECEIVE_ACTIVITY_TYPE)) {
                    logger.finest("Creating queue receiver.");
                    QueueReceiver queueReceiver = queueSession.createReceiver(queue);
                    long timeout = jmsSettings.getTimeout();
                    logger.finest("Listening for message with timeout:" + timeout);
                    TextMessage response = (TextMessage) queueReceiver.receive(timeout);

                    processEvent(
                            new JmsMessageReceivedExecEvent(scenario.getId(), "JMS message received.", new Date(),
                                    step.getId(), step.getDescription(), step.getEndpointId(), step.getRequestPath(),
                                    endpoint.getDescription(), endpoint.getUrl(), endpoint.getSettingsId(),
                                    destinationType, destinationName, response.getText()), history, listener);
                }

            } else if (destinationType.equals(JMS_TOPIC_DESTINATION_NAME)) {
                logger.fine("Looking up topic connection factory.");
                TopicConnectionFactory topicConnectionFactory = (TopicConnectionFactory) jndiContext
                        .lookup("TopicConnectionFactory");
                logger.finest("Looking up topic: " + destinationName);
                Topic topic = (Topic) jndiContext.lookup(destinationName);
                logger.finest("Creating topic connection.");
                TopicConnection topicConnection = topicConnectionFactory.createTopicConnection();
                topicConnection.start();
                logger.finest("Creating topic session.");
                TopicSession topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);

                if (activityType.equals(JMS_SEND_ACTIVITY_TYPE)) {
                    logger.finest("Creating topic publisher.");
                    TopicPublisher topicPublisher = topicSession.createPublisher(topic);
                    logger.finest("Reading file: " + step.getRequestPath());
                    String requestContent = FileUtils.readFileContent(step.getRequestPath());
                    logger.finest("Creating text message.");
                    TextMessage textMessage = topicSession.createTextMessage(requestContent);
                    logger.fine("Publishing text message.");
                    topicPublisher.send(textMessage);

                    processEvent(
                            new JmsMessageSentExecEvent(scenario.getId(), "JMS message sent.", new Date(),
                                    step.getId(), step.getDescription(), step.getEndpointId(), step.getRequestPath(),
                                    endpoint.getDescription(), endpoint.getUrl(), endpoint.getSettingsId(),
                                    destinationType, destinationName, requestContent), history, listener);

                } else if (activityType.equals(JMS_RECEIVE_ACTIVITY_TYPE)) {
                    logger.finest("Creating topic subscriber.");
                    TopicSubscriber topicReceiver = topicSession.createSubscriber(topic);
                    long timeout = jmsSettings.getTimeout();
                    logger.finest("Listening for message with timeout:" + timeout);
                    topicReceiver.receive(timeout);
                }
            } else {
                throw new UnsupportedOperationException(destinationType + " isn't supported in JMS executor plugin.");
            }
        } catch (NamingException e) {
            logger.log(Level.INFO, "Naming exception when working with JMS.", e);
            throw new ScenarioExecutionException("Naming exception when working with JMS.", e);
        } catch (JMSException e) {
            logger.log(Level.INFO, "JMS error.", e);
            throw new ScenarioExecutionException("JMS error.", e);
        } catch (IOException e) {
            logger.log(Level.INFO, "IO error during request file.", e);
            throw new ScenarioExecutionException("IO error during request file.", e);
        }

        logger.info("Ending execution in JMS executor plugin.");
    }

    /**
     * Checks whether arguments passed to this plugin are OK.
     * 
     * @param doScenarioStepPart
     * @param suite
     * @param properties
     * @param endpoint
     * @param settings
     * @param scenario
     * @param history
     * @throws IllegalArgumentException
     *             Thrown when at least one of the arguments isn't proper.
     */
    private void checkArgs(DoScenarioStepPartDeclaration doScenarioStepPart, TestsSuiteDeclaration suite,
            Properties properties, EndpointDeclaration endpoint, SettingsDeclaration settings,
            ScenarioDeclaration scenario, TestSuiteExecHistory history) {

        if (!(settings instanceof JmsSettingsPartDeclaration)) {
            throw new IllegalArgumentException("Settings must be of " + JmsSettingsPartDeclaration.class.getName()
                    + " type.");
        }

        JmsSettingsPartDeclaration jmsSettings = (JmsSettingsPartDeclaration) settings;
        if (!ValuesUtils.checkIfObjectIsInArray(jmsSettings.getDestinationType(), new String[] {
                JMS_QUEUE_DESTINATION_NAME, JMS_TOPIC_DESTINATION_NAME })) {
            throw new IllegalArgumentException(jmsSettings.getDestinationType()
                    + " structure type isn't supported by JMS plugin.");
        }

    }

    /**
     * 
     * @param event
     * @param history
     * @param listener
     */
    private static void processEvent(TestSuiteExecEvent event, TestSuiteExecHistory history,
            ITestSuiteExecutorListener listener) {
        history.addExecEvent(event);
        listener.actionPerformed(event);
    }
}
